/*****************************************************************************
 * This is tcpuart: a simple TCP/IP server redirecting in- and outgoing data
 * to a UART. After a client established a connection the listening socket
 * will be closed and any further connecting attempt are refused. When the
 * client disconnects a listening socket is opened again. Thus only one
 * connection at a time is possible (no UART sharing).
 *
 * Copyright (C) 2012  hackbeere(at)gmail.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>



/*****************************************************************************
 * Constant and default value definition
 */
#define PROC_NAME		"tcpuart"
#define UART_NAME		"/dev/ttyAMA0"
//#define UART_NAME		"/dev/ttyS0"
#define UART_BAUD		115200
#define TCP_PORT		2222
#define BUF_LEN			1024
#define MAX_DEV_NAME	64



/*****************************************************************************
 * Type definition
 */
typedef struct P_PRESET_
{
	int			uart_baud;
	char		uart_name[MAX_DEV_NAME];
	in_port_t	tcp_port;
} P_PRESET;

typedef struct P_DATA_
{
	P_PRESET	ps;
	fd_set		rd_fds;
	int			uart_id;
	int			listen_id;
	int			tcp_id;
	char		buf[BUF_LEN];
} P_DATA;



/*****************************************************************************
 * Global variable definition
 */
static int s_conn_broken = 0;



/*****************************************************************************
 * Static function declaration
 */
static void s_sig_pipe(int sig);
static void s_error_exit(const char *str);
static void s_init_data(P_DATA *pD, int argc, char *argv[]);
static void s_init_preset(P_PRESET *pP, int argc, char *argv[]);
static void s_open_uart(P_DATA *pD);
static int s_get_baud_code(int baud);
static void s_open_tcp(P_DATA *pD);
static void s_wait_conn(P_DATA *pD);
static void s_wait_input(P_DATA *pD);
static void s_transmit_data(P_DATA *pD, int inp_id, int out_id);



/*****************************************************************************
 * Main loop
 */
int main(int argc, char *argv[])
{
	P_DATA	*pD;
	P_DATA	data;


	pD = &data;
	s_init_data(pD, argc, argv);
	s_open_uart(pD);
	signal(SIGPIPE, s_sig_pipe);
	while (1)
	{
		s_open_tcp(pD);
		s_wait_conn(pD);
		while (1)
		{
			s_wait_input(pD);

			if (FD_ISSET(pD->uart_id, &pD->rd_fds))
			{
				s_transmit_data(pD, pD->uart_id, pD->tcp_id);
			}

			if (FD_ISSET(pD->tcp_id, &pD->rd_fds))
			{
				s_transmit_data(pD, pD->tcp_id, pD->uart_id);
			}

			if (s_conn_broken)
			{
				close(pD->tcp_id);
				s_conn_broken = 0;
				break;
			}
		}
	}
	return 0;
}



/*****************************************************************************
 * Signal handler called when writing to a connected socket closed by the
 * client.
 */
static void s_sig_pipe(int sig)
{
	s_conn_broken = 1;
}



/*****************************************************************************
 * Print error string and exit.
 */
static void s_error_exit(const char *str)
{
	if (errno != 0)
	{
		perror(str);
	}
	else
	{
		if ((str != 0) && (*str != '\0'))
		{
			fprintf(stderr, str);
			fprintf(stderr, "\n");
		}
	}

	fprintf(stderr, "This is '%s', a simple TCP/IP server redirecting in/output stream to UART\n", PROC_NAME);
	fprintf(stderr, "Usage:\t%s [-b baud] [-d device] [-p port]\n", PROC_NAME);
	fprintf(stderr, "\t%s -h\n", PROC_NAME);
	fprintf(stderr, "\t\t-b baud\t\tBaud rate, speed of the UART (default: %d)\n", UART_BAUD);
	fprintf(stderr, "\t\t-d device\t\tDevice name of the UART (default: %s)\n", UART_NAME);
	fprintf(stderr, "\t\t-p port\t\tPort number the server is listening on (default: %d)\n", TCP_PORT);
	fprintf(stderr, "\t\t-h\t\tThis help text\n");

	exit(1);
}



/*****************************************************************************
 * Initialize the P_DATA structure.
 */
static void s_init_data(P_DATA *pD, int argc, char *argv[])
{
	memset(pD, 0, sizeof(P_DATA));

	s_init_preset(&pD->ps, argc, argv);
}



/*****************************************************************************
 * Initialize the P_PRESET structure and parse command line arguments.
 */
static void s_init_preset(P_PRESET *pP, int argc, char *argv[])
{
	int		opt;


	pP->uart_baud = UART_BAUD;
	snprintf(pP->uart_name, sizeof(pP->uart_name), "%s", UART_NAME);
	pP->tcp_port = TCP_PORT;

	if (argc > 1)
	{
		while ((opt = getopt(argc, argv, "b:d:p:h")) != -1)
		{
			switch (opt)
			{
			case 'b':
				pP->uart_baud = atoi(optarg);
				break;
			case 'd':
				snprintf(pP->uart_name, sizeof(pP->uart_name), "%s", optarg);
				break;
			case 'p':
				pP->tcp_port = (in_port_t)atoi(optarg);
				break;
			case 'h':
			default:
				s_error_exit(0);
				break;
			}
		}
		if (argc > optind) s_error_exit("Too many arguments");
	}
}



/*****************************************************************************
 * Open and initialize the UART
 */
static void s_open_uart(P_DATA *pD)
{
	P_PRESET			*pP;
	struct termios		attr;
	speed_t				speed;
	int					fid;
	int					err;


	pP = &pD->ps;
	speed = s_get_baud_code(pP->uart_baud);
	if (speed == -1) s_error_exit("Baud rate not supported!");

	fid = open(pP->uart_name, O_RDWR | O_NOCTTY);
	if (fid == -1) s_error_exit("open()");

	err = tcgetattr(fid, &attr);
	if (err == -1) s_error_exit("tcgetattr()");

	err = cfsetspeed(&attr, speed);
	if (err == -1) s_error_exit("cfsetspeed()");

	cfmakeraw(&attr);

	err = tcsetattr(fid, TCSANOW, &attr);
	if (err == -1) s_error_exit("tcsetattr()");

	err = tcflush(fid, TCIOFLUSH);
	if (err == -1) s_error_exit("tcflush()");

	pD->uart_id = fid;
}



/*****************************************************************************
 * Determine baud code (ex. B115200).
 * Return -1 if not defined.
 */
static int s_get_baud_code(int baud)
{
	typedef struct S_BAUD_CODE { int baud; int code; } S_BAUD_CODE;

	static const S_BAUD_CODE s_baud_code[] =
	{
		{	0			,	B0			},
		{	50			,	B50			},
		{	75			,	B75			},
		{	110			,	B110		},
		{	134			,	B134		},
		{	150			,	B150		},
		{	200			,	B200		},
		{	300			,	B300		},
		{	600			,	B600		},
		{	1200		,	B1200		},
		{	1800		,	B1800		},
		{	2400		,	B2400		},
		{	4800		,	B4800		},
		{	9600		,	B9600		},
		{	19200		,	B19200		},
		{	38400		,	B38400		},
		{	57600		,	B57600		},
		{	115200		,	B115200		},
		{	230400		,	B230400		},
		{	460800		,	B460800		},
		{	500000		,	B500000		},
		{	576000		,	B576000		},
		{	921600		,	B921600		},
		{	1000000		,	B1000000	},
		{	1152000		,	B1152000	},
		{	1500000		,	B1500000	},
		{	2000000		,	B2000000	},
		{	2500000		,	B2500000	},
		{	3000000		,	B3000000	},
		{	3500000		,	B3500000	},
		{	4000000		,	B4000000	},
		{	-1			,	-1			},
	};

	const S_BAUD_CODE *pBC = s_baud_code;

	while ((pBC->baud != -1) && (pBC->baud != baud)) pBC++;
	return pBC->code;
}



/*****************************************************************************
 * Open socket and set it to listen state.
 */
static void s_open_tcp(P_DATA *pD)
{
	struct sockaddr_in		sa;
	int						sid;
	int						on;
	int						err;


	sid = socket(AF_INET, SOCK_STREAM, 0);
	if (sid == -1) s_error_exit("socket()");

	on = 1;
	err = setsockopt(sid, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	if (err == -1) s_error_exit("setsockopt()");

	memset(&sa, 0, sizeof(sa));

	sa.sin_family      = AF_INET;
	sa.sin_addr.s_addr = htonl(INADDR_ANY);
	sa.sin_port        = htons(pD->ps.tcp_port);

	err = bind(sid, (struct sockaddr*)&sa, sizeof(sa));
	if (err == -1) s_error_exit("bind()");

	err = listen(sid, 1);
	if (err == -1) s_error_exit("listen()");

	pD->listen_id = sid;
}



/*****************************************************************************
 * Wait for a connection by a client.
 * Close listen socket after connection.
 */
static void s_wait_conn(P_DATA *pD)
{
	struct sockaddr_in		sa;
	socklen_t				len;
	int						sid;


	len = sizeof(sa);

	sid = accept(pD->listen_id, (struct sockaddr*)&sa, &len);
	if (sid == -1) s_error_exit("accept()");

	close(pD->listen_id);
	pD->tcp_id = sid;
}



/*****************************************************************************
 * Wait for incoming data on TCP or UART using select().
 */
static void s_wait_input(P_DATA *pD)
{
	int		nfds;
	int		err;


	FD_ZERO(&pD->rd_fds);
	FD_SET(pD->uart_id, &pD->rd_fds);
	FD_SET(pD->tcp_id, &pD->rd_fds);

	nfds = (pD->uart_id > pD->tcp_id) ? pD->uart_id : pD->tcp_id;
	nfds++;

	err = select(nfds, &pD->rd_fds, 0, 0, 0);
	if (err == -1) s_error_exit("select()");
}



/*****************************************************************************
 * Transmit data from input to output file descriptor.
 */
static void s_transmit_data(P_DATA *pD, int inp_id, int out_id)
{
	ssize_t		rec_len, snd_len, sent;
	int			err;


	err = -1;
	rec_len = read(inp_id, pD->buf, sizeof(pD->buf));
	if (rec_len == -1) s_error_exit("read()");

	if (rec_len == 0)
	{
		s_conn_broken = 1;
	}
	else
	{
		sent = 0;
		do
		{
			snd_len = write(out_id, &pD->buf[sent], rec_len - sent);
			if (snd_len == -1)
			{
				if (s_conn_broken) break;
				else s_error_exit("write()");
			}

			sent += snd_len;
		} while ((rec_len - sent) > 0);
	}
}



